#ifndef __UNITMANAGER_HPP__
#define __UNITMANAGER_HPP__ 1

#include <cstdint>
#include <unordered_map>
#include <stack>
#include <string>

#include "Unit.hpp"

using namespace std;

enum class StatId;

/* @brief CUnitManager class */

class CUnitManager
{
public:
	/* @brief typedefs */
	typedef unordered_map<int32_t, Unit<UnitPlayer>*> PlayerMap;
	typedef unordered_map<int32_t, Unit<UnitMonster>*> MonsterMap;
	typedef unordered_map<int32_t, Unit<UnitMissile>*> MissileMap;
	typedef unordered_map<int32_t, Unit<UnitItem>*> ItemMap;
	typedef unordered_map<int32_t, Unit<UnitObject>*> ObjectMap;

	typedef stack<Unit<UnitPlayer>*> PlayerUpdates;
	typedef stack<Unit<UnitMonster>*> MonsterUpdates;
	typedef stack<Unit<UnitMissile>*> MissileUpdates;
	typedef stack<Unit<UnitItem>*> ItemUpdates;
	typedef stack<Unit<UnitObject>*> ObjectUpdates;

	CUnitManager();
	~CUnitManager();

	const CUnitManager& operator=(const CUnitManager &) const { return *this; }

	Unit<UnitPlayer>* InitPlayer(int32_t playerLevel, PlayerClass playerClass, PlayerSex playerSex);
	Unit<UnitMonster>* InitMonster(int32_t monsterLevel);
	Unit<UnitMissile>* InitMissile(int32_t missileLevel);
	Unit<UnitItem>* InitItem(int32_t itemLevel, ItemClass itemClass);
	Unit<UnitObject>* InitObject();
	Unit<UnitPlayer>* CreatePlayer(string playerName, PlayerClass playerClass, PlayerSex playerSex);

	template<typename T> bool AddLink(Unit<T>* unit);
	template<> bool AddLink<UnitPlayer>(Unit<UnitPlayer>* unit);
	template<> bool AddLink<UnitMonster>(Unit<UnitMonster>* unit);
	template<> bool AddLink<UnitMissile>(Unit<UnitMissile>* unit);
	template<> bool AddLink<UnitItem>(Unit<UnitItem>* unit);
	template<> bool AddLink<UnitObject>(Unit<UnitObject>* unit);

	template<typename T> bool RemoveLink(int32_t unitId);

	template<> bool RemoveLink<UnitPlayer>(int32_t unitId);
	template<> bool RemoveLink<UnitMonster>(int32_t unitId);
	template<> bool RemoveLink<UnitMissile>(int32_t unitId);
	template<> bool RemoveLink<UnitItem>(int32_t unitId);
	template<> bool RemoveLink<UnitObject>(int32_t unitId);

	template <typename T> typename enable_if<is_same<T, UnitPlayer>::value>::type* FindLink(int32_t unitId)
	{
		return playerMap[unitId];
	}

	template <typename T> typename enable_if<is_same<T, UnitMonster>::value>::type*  FindLink(int32_t unitId)
	{
		return monsterMap[unitId];
	}

	//template <typename T> inline const PlayerMap& GetPlayerMap() const { return playerMap; }

	inline PlayerUpdates& UpdateVector(void) { return playerUpdates; }
	bool UpdateUnits(void);

	template <typename T>
	Unit<UnitItem>* AddInventoryItem(typename enable_if<is_same<T, Unit<UnitPlayer>>::value || is_same<T, Unit<UnitMonster>>::value>::type* unit, Unit<UnitItem>* item)
	{
		if(item->ownerId)
			throw;

		Inventory* inv = new Inventory;
		item->ownerId = unit->unitId;
		inv->item = item;
		inv->ownerId = unit;

		unit->inventory.insert(InventoryPair(item->unitId, inv));

		return item;
	}

	template <typename T> bool RemoveInventoryItem(typename enable_if<is_same<T, UnitPlayer>::value || is_same<T, UnitMonster>::value>::type* unit, Unit<UnitItem>* item)
	{
		if(item->ownerId != unit->unitId)
			throw;

		unit->inventory.erase(item->unitId);

		item->ownerId = -1;

		return true;
	}

	template<typename T> Stat* CreateUnitStat(Unit<T>* unit, StatId statId, int32_t statVal = INT32_MIN)
	{
		Stat* stat = new Stat;
		stat->ownerId = unit->unitId;
		stat->statId = statId;
		stat->statVal = statVal;

		unit->statList.insert(pair<std::underlying_type<StatId>::type, Stat*>(static_cast<std::underlying_type<StatId>::type>(statId), stat));
		return stat;
	}

	template<typename T> void RemoveUnitStat(Stat* stat)
	{
		auto unit = playerMap[stat->ownerId];
		if(!unit || !unit->statList.erase(stat->statId))
		{
			delete stat;
			throw;
		}
		delete stat;
	}

	template<typename T> Stat* GetUnitStat(Unit<T>* unit, StatId statId) const
	{
		return unit->statList[statId];
	}

	template<typename T> int32_t CalcStat(Unit<T>* unit, StatId statId) // Bollocks
	{
		if(statId < StatId::STAT_FIRST || statId > StatId::STAT_LAST)
			throw;

		int32_t statVal = 0;

		auto stat = unit->statList[(int32_t)statId];
		if(stat == nullptr)
			return statVal;

		statVal = stat->statVal;

		auto invBegin = unit->inventory.cbegin();
		auto invEnd = unit->inventory.cend();

		for(; invBegin != invEnd; ++invBegin)
		{
			if((invBegin->second) == nullptr)
				throw;
			if((invBegin->second)->ownerId == unit->unitId)
			{
				stat = unit->statList[(int32_t)statId];
				if(stat && stat->ownerId == unit->unitId)
					statVal += stat->statVal;
			}
		}

		return statVal;
	}

private:
	PlayerMap playerMap;
	MonsterMap monsterMap;
	MissileMap missileMap;
	ItemMap itemMap;
	ObjectMap objectMap;

	PlayerUpdates playerUpdates;
	MonsterUpdates monsterUpdates;
	MissileUpdates missileUpdates;
	ItemUpdates itemUpdates;
	ObjectUpdates objectUpdates;

	int32_t nextUnitId;
};

template<> bool CUnitManager::AddLink<UnitPlayer>(Unit<UnitPlayer>* unit)
{
	if(!unit)
		return false;

	unit->unitId = ++nextUnitId;

	return playerMap.insert(make_pair(unit->unitId, unit)).second;
}

template<> bool CUnitManager::AddLink<UnitMonster>(Unit<UnitMonster>* unit)
{
	if(!unit)
		return false;

	unit->unitId = ++nextUnitId;

	return monsterMap.insert(make_pair(unit->unitId, unit)).second;
}

template<> bool CUnitManager::AddLink<UnitItem>(Unit<UnitItem> *unit)
{
	if(!unit)
		return false;

	unit->unitId = ++nextUnitId;

	return itemMap.insert(make_pair(unit->unitId, unit)).second;
}

template<> bool CUnitManager::AddLink<UnitMissile>(Unit<UnitMissile>* unit)
{
	if(!unit)
		return false;

	unit->unitId = ++nextUnitId;

	return missileMap.insert(make_pair(unit->unitId, unit)).second;
}

template<> bool CUnitManager::AddLink<UnitObject>(Unit<UnitObject>* unit)
{
	if(!unit)
		return false;

	unit->unitId = ++nextUnitId;

	return objectMap.insert(make_pair(unit->unitId, unit)).second;
}

template<> bool CUnitManager::RemoveLink<UnitPlayer>(int32_t unitId)
{
	auto unit = playerMap[unitId];
	if(!unit)
		return false;

	playerMap.erase(unit->unitId);
	delete unit;

	return true;
}

template<> bool CUnitManager::RemoveLink<UnitMonster>(int32_t unitId)
{
	auto unit = monsterMap[unitId];
	if(!unit)
		return false;

	monsterMap.erase(unit->unitId);
	delete unit;

	return true;
}

template<> bool CUnitManager::RemoveLink<UnitMissile>(int32_t unitId)
{
	auto unit = missileMap[unitId];
	if(!unit)
		return false;

	missileMap.erase(unit->unitId);
	delete unit;

	return true;
}

template<> bool CUnitManager::RemoveLink<UnitItem>(int32_t unitId)
{
	auto unit = itemMap[unitId];
	if(!unit)
		return false;

	itemMap.erase(unit->unitId);
	delete unit;

	return true;
}

template<> bool CUnitManager::RemoveLink<UnitObject>(int32_t unitId)
{
	auto unit = objectMap[unitId];
	if(!unit)
		return false;

	objectMap.erase(unit->unitId);
	delete unit;

	return true;
}

#endif
